import java.io.*;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;
import java.util.Random;

public class GameWorld {
	public static Random rnd = new Random();

	// Level's data
	// 0-layer - tiles
	public static byte bTilesMatrix[][];
   
	// 1-layer - decoration objects
	//#if !IMGGROUP_12x12xLimited
		public static LevObj decorObjects[];
	//#endif
   
	// 2-layer - dynamic objects
	public static LevObj dynamicObjects[];

	// Monsters layer
	public static Monster monsters[];

	// hero starting point
	public static int nStartX;
	public static int nStartY;

	// screen's world position
	public static int nWndCX = 0;
	public static int nWndCY = 0;

	// game play screen size
	public static int gps_width;
	public static int gps_height;

	//#if BACK_BUFFER_FULL || BACK_BUFFER_LEVEL
		private static Image iBackBuffer = null;
		private static Graphics gBackBuffer = null;
		private static int bbLeft;
		private static int bbTop;
		private static int bbWidth;
		private static int bbHeight;
	//#endif

	// gameplay params
	public static int m_nTotalBlocks = 0;
	public static int m_nRepaintedBlocks = 0;
	public static int nCompleteTicks = 0;
	public static int nLevelTicks = 0;
	public static int nInitalLevelTicks = 0;
	public static int n_levels = 0;
	public static String levelName;

	//#if USE_GAME_TIPS
		public static byte gameTips[] = new byte[Constants.TIP_COUNT];
	//#endif

	// correction screen shift
	public static final int CORRECTION_COUNT = 10;

	public static int shiftXPos = 0;
	public static int shiftYPos = 0;
	public static int shiftXInd = CORRECTION_COUNT;
	public static int shiftYInd = CORRECTION_COUNT;
	private static long correctionArray[];

   static {
		long A, B;
		correctionArray = new long[CORRECTION_COUNT];
		for (int i = 0; i < CORRECTION_COUNT; i++) {
			A = FP.div(FP.toFP(i), FP.toFP(CORRECTION_COUNT));
			B = FP.toFP(1) - A;
			correctionArray[i] = FP.div(FP.mul(FP.toFP(2), FP.mul(FP.toFP(0), FP.mul(A, B))) + FP.mul(FP.toFP(CORRECTION_COUNT), FP.mul(B, B)), FP.toFP(CORRECTION_COUNT));
		}
	}

	/**
	 *
	 */
	public static void CleanUp() {
		bTilesMatrix = null;
		monsters = null;
		//#if !IMGGROUP_12x12xLimited
			decorObjects = null;
		//#endif
		dynamicObjects = null;
		System.gc();
	}

	/**
	 *
	 */
	public static int GetNextTeleport(int index) {
		int i = index;
		do {
			if (i < dynamicObjects.length - 1) i++;
			else i = 0;
		}
		while (i != index && dynamicObjects[i].nClassID != ObjID.nObjID_Teleport);
		if ((i >= 0) && (i < dynamicObjects.length)) {
			return i;
		}
		return -1;
	}

	/**
	 *
	 */
	public static int IsIntersectLevObj(int left, int top, int right, int bottom, boolean bl_vert, boolean bl_right, boolean bl_down) {
		int i;
		int n_ret_code = Hero.COLLIS_RET_NONE; // no collision

		if (dynamicObjects != null) {
			for (i = 0; i < dynamicObjects.length && n_ret_code != Hero.COLLIS_RET_STOP; i++) {
				switch (dynamicObjects[i].nClassID) {
					case ObjID.nObjID_Bonus:
						if (dynamicObjects[i].m_blActive) {
							if (dynamicObjects[i].IsIntersect(left, top, right, bottom)) {
								Hero.OnLevObjCollis(i, bl_vert, bl_right, bl_down);
							}
						}
					break;

					case ObjID.nObjID_Ladder:
						if (dynamicObjects[i].IsIntersect(left, bottom - 1, right, bottom)) {
							n_ret_code = Hero.OnLevObjCollis(i, bl_vert, bl_right, bl_down);
							if (bl_vert && bl_down && (Hero.m_nPosY <= dynamicObjects[i].m_nPosY)) {
								n_ret_code = Hero.COLLIS_RET_NO_MOVING;
							}
						}
					break;

					case ObjID.nObjID_Teleport:
					case ObjID.nObjID_Slide:
					case ObjID.nObjID_Cannon:
					case ObjID.nObjID_Spring:
					case ObjID.nObjID_Firewall:
						if (dynamicObjects[i].IsIntersect(left, bottom - 1, right, bottom)) {
							n_ret_code = Hero.OnLevObjCollis(i, bl_vert, bl_right, bl_down);
						}
					break;

					case ObjID.nObjID_CrashingPlatform:
						if (dynamicObjects[i].IsIntersect(left, top, right, bottom)) {
							n_ret_code = Hero.OnLevObjCollis(i, bl_vert, bl_right, bl_down);
						}
						else if ((Hero.m_bCrashingPlatform == true) && (Hero.objCurrentIndex == i)) {
							if (dynamicObjects[i].objState == Constants.CRASHING_PLATFORM_STATE_FALL) {
								dynamicObjects[i].objState = Constants.CRASHING_PLATFORM_STATE_SLOW_DOWN;
								dynamicObjects[i].animationCurrent.Tick();
							}
							else if(dynamicObjects[i].objState == Constants.CRASHING_PLATFORM_STATE_MARV_STEP) {
								dynamicObjects[i].objState = Constants.CRASHING_PLATFORM_STATE_SLOW_DOWN;
							}
							Hero.m_bCrashingPlatform = false;
							Hero.m_bJumpEnable = false;
							int inertia = dynamicObjects[i].objStepY / Constants.CRASHING_PLATFORM_STEP_INC;
							if (inertia > Hero.c_nSpeedJump) {
								inertia = Hero.c_nSpeedJump;
							}
							Hero.m_nSpeed45Degree = (int) FP.toFP(inertia);
							Hero.m_nFlightDir = Constants.DIRECTION_FLY_DOWN;
						}
					break;

					case ObjID.nObjID_TransporterHorz:
					case ObjID.nObjID_TransporterVert:
					case ObjID.nObjID_Transporter45Degree:
						if (dynamicObjects[i].IsIntersect(left, top, right, bottom)) {
							n_ret_code = Hero.OnLevObjCollis(i, bl_vert, bl_right, bl_down);
						}
						else if((Hero.m_bTransporter == true) && (Hero.objCurrentIndex == i)) {
							Hero.m_bTransporter = false;
							Hero.m_bJumpEnable = false;
							if (dynamicObjects[i].objState != Constants.TRANSPORTER_STATE_WAIT) {
								Hero.m_nSpeed45Degree = (int) FP.toFP(Constants.TRANSPORTER_STEP_X + 1);
								Hero.m_nFlightDir = 0;
								
								if (dynamicObjects[i].objStepX > 0) {
									Hero.m_nFlightDir = Constants.DIRECTION_FLY_RIGHT;
								}
								else if (dynamicObjects[i].objStepX < 0) {
									Hero.m_nFlightDir = Constants.DIRECTION_FLY_LEFT;
								}

								if (dynamicObjects[i].objStepY > 0) {
									Hero.m_nFlightDir |= Constants.DIRECTION_FLY_DOWN;
								}
								else if (dynamicObjects[i].objStepY < 0) {
									Hero.m_nFlightDir |= Constants.DIRECTION_FLY_UP;
								}
							}
                        }
					break;
				}
			}
		}
		return n_ret_code;
	}

	/**
	 * return 0 - no collision
     * return 0xf0f0f0f0 - collision
	 * return not 0 - dy correction for marv sprite
	 */
	public static final int nRet_CantMove =0xf0f0f0f0;
	public static int IsIntersect(int left, int top, int right, int bottom, boolean bl_vert, boolean bl_right, boolean bl_down, boolean bl_paint) {
		if ((left >= 0) && (top>=0)) {
			int n_blk_left = left / TileDrawer.nTileWidth;
			int n_blk_right = right / TileDrawer.nTileWidth;
			int n_blk_top = top / TileDrawer.nTileWidth;
			int n_blk_bottom = bottom / TileDrawer.nTileWidth;

			if ((n_blk_right < bTilesMatrix.length) && (n_blk_bottom < bTilesMatrix[0].length)) {
				int n_blk_bottom_per = 1 + bottom - (n_blk_bottom * TileDrawer.nTileWidth);
				int n_blk_left_per = (left - (n_blk_left * TileDrawer.nTileWidth)) / TileDrawer.nTileHVDiv;
				int n_blk_right_per = (right - (n_blk_right * TileDrawer.nTileWidth)) / TileDrawer.nTileHVDiv;

				// vertical moving
				if (bl_vert) {
					boolean canMove = true;   // is false if same side (left, right) detect collision
					for (int n_x = n_blk_left; n_x <= n_blk_right; n_x++) {
						int n_tile;
						if (bl_down) {
							n_tile = bTilesMatrix[n_x][n_blk_bottom];
						}
						else { // up moving
							if(top == 0) return nRet_CantMove;
							n_tile = bTilesMatrix[n_x][n_blk_top];
						}
						TileIndex tile = TileDrawer.mTiles[n_tile];

						if ((tile.bOptions & TileDrawer.bOpt_Collisable) != 0) { // detect collision with collisable tile
							// solid blocks is have'nt mask
							if ((tile.bOptions & TileDrawer.bOpt_HaveMask) == 0) return nRet_CantMove;

							if (bl_down) { // down moving
								// processing of cutted blocks
								int n_blk_per;// = (n_x == n_blk_left) ? n_blk_left_per : n_blk_right_per;

								if (n_blk_left == n_blk_right) {
									if (tile.bHeight[0] > tile.bHeight[tile.bHeight.length-1])
										n_blk_per = n_blk_right_per;
									else
										n_blk_per = n_blk_left_per;
								}
								else {
									if (n_x == n_blk_left) {
										if (tile.bHeight[0] > tile.bHeight[tile.bHeight.length-1])
											n_blk_per = tile.bHeight.length-1;
										else
											n_blk_per = n_blk_left_per;
									}
									else {
										if (tile.bHeight[0] > tile.bHeight[tile.bHeight.length-1])
											n_blk_per = n_blk_right_per;
										else
											n_blk_per = 0;
									}
								}

								if ((platformType(tile.nPicID, false) != PicID.nPicID_ladder_tile_full) && (tile.bHeight[n_blk_per] <= n_blk_bottom_per) && ((n_blk_bottom_per - tile.bHeight[n_blk_per]) <= 1)) {
									if (bl_paint) {
										if ((n_tile < TilesID.nTileID_TilesCount) &&
										//#if !IMGGROUP_12x12xLimited
											(n_tile > TilesID.nTileID_bgs11) &&
										//#else
											(n_tile > TilesID.nTileID_bg5) &&
										//#endif
											((n_tile & 1) == 0)) // block is not repainted
										{
											bTilesMatrix[n_x][n_blk_bottom] = (byte)(n_tile + 1);
											m_nRepaintedBlocks++;
											Hero.m_nTotalScores += Constants.SCORE_PLATFORM;

											//#if BACK_BUFFER_FULL || BACK_BUFFER_LEVEL
												int x = n_x * TileDrawer.nTileWidth - bbLeft;
												int y = n_blk_bottom * TileDrawer.nTileWidth - bbTop;

												//#if TILESTYPE_SEPARATE
													TileDrawer.DrawTile(gBackBuffer, x, y, bTilesMatrix[n_x][n_blk_bottom]);
												//#else
													PicIndex picIndex = PicDrawer.mIndex[TileDrawer.mTiles[bTilesMatrix[n_x][n_blk_bottom]].nPicID];
													gBackBuffer.setClip(x, y, picIndex.w, picIndex.h);
													gBackBuffer.drawImage(PicDrawer.mImages[picIndex.nImgID], x - picIndex.x, y - picIndex.y, Graphics.TOP | Graphics.LEFT);
												//#endif
											//#endif
										}
										int ndx = n_blk_bottom + 1;
										if (ndx < bTilesMatrix[0].length) {
											n_tile = bTilesMatrix[n_x][ndx];
											if ((n_tile < TilesID.nTileID_TilesCount) &&
											//#if !IMGGROUP_12x12xLimited
												(n_tile > TilesID.nTileID_bgs11) &&
											//#else
												(n_tile > TilesID.nTileID_bg5) &&
											//#endif
												(platformType(TileDrawer.mTiles[n_tile].nPicID, false) == PicID.nPicID_ladder_tile_full) &&
												((n_tile & 1) == 0)) // block is not repainted
											{
												bTilesMatrix[n_x][ndx] = ( byte ) ( n_tile + 1 );
												m_nRepaintedBlocks++;

												//#if BACK_BUFFER_FULL || BACK_BUFFER_LEVEL
													int x = n_x * TileDrawer.nTileWidth - bbLeft;
													int y = ndx * TileDrawer.nTileWidth - bbTop;

													//#if TILESTYPE_SEPARATE
														TileDrawer.DrawTile(gBackBuffer, x, y, bTilesMatrix[n_x][ndx]);
													//#else
														PicIndex picIndex = PicDrawer.mIndex[TileDrawer.mTiles[bTilesMatrix[n_x][ndx]].nPicID];
														gBackBuffer.setClip(x, y, picIndex.w, picIndex.h);
														gBackBuffer.drawImage(PicDrawer.mImages[picIndex.nImgID], x - picIndex.x, y - picIndex.y, Graphics.TOP | Graphics.LEFT);
													//#endif
												//#endif
											}
										}
									}
									canMove = false;
								}
							}
						}
					}

					if (canMove) {
						return 0;
					}
					else {
						return nRet_CantMove;
					}
				}
				else { // horzontal moving
					for (int n_y = n_blk_bottom; n_y >= n_blk_top; n_y--) {
						int n_x = n_blk_left;
						int ndx = n_blk_left_per;
						if (bl_right) {
							n_x = n_blk_right;
							ndx = n_blk_right_per;
						}

						int n_tile = bTilesMatrix[n_x][n_y];
						TileIndex tile = TileDrawer.mTiles[n_tile];
						if ((tile.bOptions & TileDrawer.bOpt_Collisable) != 0) { // detect collision with collisable tile
							// solid blocks is have'nt mask
							if ((tile.bOptions & TileDrawer.bOpt_HaveMask) == 0) {
								return nRet_CantMove;
							}

							if ((tile.bHeight[ndx] <= n_blk_bottom_per) &&
								((n_blk_bottom_per - tile.bHeight[ndx]) <= 2) &&
								(n_y == n_blk_bottom) &&
								(tile.bHeight[0] != tile.bHeight[tile.bHeight.length-1])) {
								return (tile.bHeight[ndx] - n_blk_bottom_per);
							}
						}
					}
				}
				return 0;
			}
		}
		return 0;
	}

	/**
	 *
	 */
	public static int Tick(byte b_frm) {
		int i;
		nWndCX = Hero.m_nPosX;
		nWndCY = Hero.m_nPosY;

		// slow shift of screen
		if (shiftXInd < CORRECTION_COUNT) {
			nWndCX += FP.toInt(shiftXPos * correctionArray[shiftXInd]);
			shiftXInd++;
		}

		if (shiftYInd < CORRECTION_COUNT) {
			nWndCY += FP.toInt(shiftYPos * correctionArray[shiftYInd]);
			shiftYInd++;
		}

		int ret = GWRET_NONE;

		if (nCompleteTicks == 0) {
			if (nLevelTicks > 0) {
				if ((b_frm & 0x0f) == 0) {
					nLevelTicks--;

					if (nLevelTicks == 9) {
						Hero.timeAlarm = true;
					}

					//#if SOUND_MMAPI || SOUND_NOKIA_RT || SAMSUNG_MMF || SHARP_MMF
						if (nLevelTicks == 3) {
							MTMCanvas.soundPlay(Constants.SOUND_TIMEWARNING);
						}
					//#endif
				}
            
				ret = Hero.Tick(b_frm);

				if (m_nRepaintedBlocks >= m_nTotalBlocks) {
					nCompleteTicks = nLevelTicks;
					if (nCompleteTicks < 30) {
						nCompleteTicks = 30;
					}

					Hero.ForceStop();
					//#if !IMGGROUP_12x12xLimited
						Hero.setAnim(Hero.c_nAnimID_Turn);
					//#endif
					Hero.m_blChangeAnimEnable = false;

					Hero.m_nDeathTicks = nCompleteTicks + 1;
					Hero.m_nImmortalTicks = 0;
					Hero.m_blVisible = true;

					//#if SOUND_MMAPI || SOUND_NOKIA_RT || SAMSUNG_MMF || SHARP_MMF
						MTMCanvas.soundPlay(Constants.SOUND_LVL_CLEAR);
					//#endif

					GameMenu.strToText(StringsID.nStrID_LevComplete, false);
				}
			}
			else // time is over
			{
				//#if !IMGGROUP_12x12xLimited
					Hero.ForceDeath( Hero.c_nAnimID_TODeath );
				//#else
					Hero.ForceDeath( Hero.c_nAnimID_Death );
				//#endif
				nCompleteTicks = 30;
			}
		}
 
		if (nCompleteTicks > 1) {
			Hero.Tick(b_frm);
			nCompleteTicks--;

			if (nLevelTicks > 0) {
				Hero.m_nTotalScores++;
				nLevelTicks--;
			}
		}
		else if (nCompleteTicks == 1) {
			nCompleteTicks = 0;

			if (nLevelTicks >= 1) {
				Hero.m_nTotalScores += nLevelTicks;
			}
			return (Hero.m_nImmortalTicks == 0) ? GWRET_COMPLETE : GWRET_TIME_OVER;
		}

		//#if !IMGGROUP_12x12xLimited
			if (decorObjects != null) {
				for (i = 0; i < decorObjects.length; i++) {
					decorObjects[i].Tick(b_frm);
				}
			}
		//#endif

		if (dynamicObjects != null) {
			for (i = 0; i < dynamicObjects.length; i++) {
				dynamicObjects[i].Tick(b_frm);
			}
		}

		// Monsters update
		if (monsters != null) {
			for (i = 0; i < monsters.length; i++) {
				monsters[i].update();
			}
		}
		return ret;
	}

	/**
	 *
	 */
	public static void Draw(Graphics g) {
		int i = 0, j = 0;

		// cut displayable part of game play screen
		int hw = MTMCanvas.width >> 1;
		int hh = MTMCanvas.height >> 1;

		int left = nWndCX - hw;
		int top = nWndCY - hh;
		int right, bottom;

		if (left < 0) {
			left = 0;
			right = MTMCanvas.width;
		}
		else {
			right = left + MTMCanvas.width;

			if (right >= gps_width) {
				right = gps_width - 1;
				left = right - MTMCanvas.width;
			}
		}

		if (top < 0) {
			top = 0;
			bottom = MTMCanvas.height;
		}
		else {
			bottom = top + MTMCanvas.height;

			if (bottom >= gps_height) {
				bottom = gps_height - 1;
				top = bottom - MTMCanvas.height;
			}
		}

		//#if BACK_BUFFER_FULL || BACK_BUFFER_LEVEL
			int x = 0, y = 0;
			boolean flag = false;
			int shiftWidth = 0, addWidth = 0, startIdx = 0;

			// X-axis SCROLL
			if (left < bbLeft) {
				addWidth = ((bbLeft - left) / TileDrawer.nTileWidth) + 1;
				shiftWidth = addWidth * TileDrawer.nTileWidth;

				bbLeft -= shiftWidth;
				if (bbLeft < 0) {
					shiftWidth += bbLeft;   // turn to zero
					bbLeft = 0;
					addWidth = shiftWidth / TileDrawer.nTileWidth;
				}

				for (x = bbWidth - shiftWidth; x >= 0; x-=shiftWidth) {
					gBackBuffer.setClip(x, 0, shiftWidth, bbHeight);
					gBackBuffer.drawImage(iBackBuffer, shiftWidth, 0, Graphics.TOP|Graphics.LEFT);
				}

				x = 0;
				startIdx = bbLeft / TileDrawer.nTileWidth;

				flag = true;
			}
			else if(right > (bbLeft + bbWidth)) {
				addWidth = ((right - (bbLeft + bbWidth)) / TileDrawer.nTileWidth) + 1;
				shiftWidth = addWidth * TileDrawer.nTileWidth;

				bbLeft += shiftWidth;
				if ((bbLeft + bbWidth) > gps_width) {
					shiftWidth -= gps_width - (bbLeft + bbWidth);   // turn to gps_width
					bbLeft = gps_width - bbWidth;
					addWidth = shiftWidth / TileDrawer.nTileWidth;
				}

				for (x = 0; x < bbWidth; x+=shiftWidth) {
					gBackBuffer.setClip(x, 0, shiftWidth, bbHeight);
					gBackBuffer.drawImage(iBackBuffer, -shiftWidth, 0, Graphics.TOP|Graphics.LEFT);
				}

				x = bbWidth - shiftWidth;
				startIdx = ((bbLeft + bbWidth - 1) / TileDrawer.nTileWidth) - addWidth + 1;

				flag = true;
            }

			if (flag) {
				PicIndex picIndex;
				int tile_t = bbTop / TileDrawer.nTileWidth;
				int tile_b = (bbTop + bbHeight) / TileDrawer.nTileWidth;

				//#if TILESTYPE_SEPARATE
					gBackBuffer.setClip(0, 0, bbWidth, bbHeight);
				//#endif

				for (i = startIdx; i < (startIdx+addWidth); i++) {
					for (j = tile_t, y = 0; j < tile_b; j++, y += TileDrawer.nTileWidth) {
						//#if TILESTYPE_SEPARATE
							TileDrawer.DrawTile(gBackBuffer, x, y, bTilesMatrix[i][j]);
						//#else
							int n_tile_id = bTilesMatrix[i][j];
							if ((n_tile_id >= 0) && (n_tile_id < TileDrawer.mTiles.length)) {
								picIndex = PicDrawer.mIndex[TileDrawer.mTiles[n_tile_id].nPicID];
								gBackBuffer.setClip(x, y, picIndex.w, picIndex.h);
								gBackBuffer.drawImage(PicDrawer.mImages[picIndex.nImgID], x - picIndex.x, y - picIndex.y, Graphics.TOP | Graphics.LEFT);
							}
						//#endif
					}
					x += TileDrawer.nTileWidth;
				}
			}

			// Y-axis SCROLL
			flag = false;
			if (top < bbTop) {
				addWidth = ((bbTop - top) / TileDrawer.nTileWidth) + 1;
				shiftWidth = addWidth * TileDrawer.nTileWidth;

				bbTop -= shiftWidth;
				if (bbTop < 0) {
					shiftWidth += bbTop;    // turn to zero
					bbTop = 0;
					addWidth = shiftWidth / TileDrawer.nTileWidth;
				}

				for (y = bbHeight - shiftWidth; y >= 0; y-=shiftWidth) {
					gBackBuffer.setClip(0, y, bbWidth, shiftWidth);
					gBackBuffer.drawImage(iBackBuffer, 0, shiftWidth, Graphics.TOP|Graphics.LEFT);
				}

				y = 0;
				startIdx = bbTop / TileDrawer.nTileWidth;

				flag = true;
			}
			else if(bottom > (bbTop + bbHeight)) {
				addWidth = ((bottom - (bbTop + bbHeight)) / TileDrawer.nTileWidth) + 1;
				shiftWidth = addWidth * TileDrawer.nTileWidth;

				bbTop += shiftWidth;
				if ((bbTop + bbHeight) > gps_height) {
					shiftWidth -= gps_height - (bbTop + bbHeight);  // turn to gps_height
					bbTop = gps_height - bbHeight;
					addWidth = shiftWidth / TileDrawer.nTileWidth;
				}

				for (y = 0; y < bbHeight; y+=shiftWidth) {
					gBackBuffer.setClip(0, y, bbWidth, shiftWidth);
					gBackBuffer.drawImage(iBackBuffer, 0, -shiftWidth, Graphics.TOP|Graphics.LEFT);
				}

				y = bbHeight - shiftWidth;
				startIdx = ((bbTop + bbHeight - 1) / TileDrawer.nTileWidth) - addWidth + 1;

				flag = true;
            }

			if (flag) {
				PicIndex picIndex;
				int tile_l = bbLeft / TileDrawer.nTileWidth;
				int tile_r = (bbLeft + bbWidth) / TileDrawer.nTileWidth;

				//#if TILESTYPE_SEPARATE
					gBackBuffer.setClip(0, 0, bbWidth, bbHeight);
				//#endif

				for (j = startIdx; j < (startIdx + addWidth); j++) {
					for (i = tile_l, x = 0; i < tile_r; i++, x += TileDrawer.nTileWidth) {
					//#if TILESTYPE_SEPARATE
						TileDrawer.DrawTile(gBackBuffer, x, y, bTilesMatrix[i][j]);
					//#else
						int n_tile_id = bTilesMatrix[i][j];
						if ((n_tile_id >= 0) && (n_tile_id < TileDrawer.mTiles.length)) {
							picIndex = PicDrawer.mIndex[TileDrawer.mTiles[n_tile_id].nPicID];
							gBackBuffer.setClip(x, y, picIndex.w, picIndex.h);
							gBackBuffer.drawImage(PicDrawer.mImages[picIndex.nImgID], x - picIndex.x, y - picIndex.y, Graphics.TOP | Graphics.LEFT);
						}
					//#endif
					}
					y += TileDrawer.nTileWidth;
				}
			}

			g.setClip(0, 0, MTMCanvas.width, MTMCanvas.height);
			g.drawImage(iBackBuffer, bbLeft - left, bbTop - top, Graphics.TOP|Graphics.LEFT);
		//#else
			// convert coordinates to tile's indexes
			int tile_l = left / TileDrawer.nTileWidth;
			int tile_r = right / TileDrawer.nTileWidth;
			int tile_t = top / TileDrawer.nTileWidth;
			int tile_b = bottom / TileDrawer.nTileWidth;

			// set inital position for draw background
			int x = (tile_l * TileDrawer.nTileWidth) - left;
			int y = (tile_t * TileDrawer.nTileWidth) - top;

			// Background
			for (i = tile_l; i <= tile_r; i++, x += TileDrawer.nTileWidth) {
				int yy = y;
				for (j = tile_t; j <= tile_b; j++, yy += TileDrawer.nTileWidth) {
					TileDrawer.DrawTile(g, x, yy, bTilesMatrix[i][j]);
				}
			}
		//#endif

		//#if !IMGGROUP_12x12xLimited
			// Decoration objects
			if (decorObjects != null) {
				for (i = 0; i < decorObjects.length; i++) {
					decorObjects[i].Draw(g, left, top);
				}
			}
		//#endif

		// Dynamic objects
		LevObj.teleportCount = 0;
		if (dynamicObjects != null) {
			for (i = 0; i < dynamicObjects.length; i++) {
				dynamicObjects[i].Draw(g, left, top);
			}
		}

		// Marv
		Hero.Draw( g, left, top);

		// Monsters
		if (monsters != null) {
			for (i = 0; i < monsters.length; i++) {
				monsters[i].draw(g, left, top);
			}
		}

		// Statistics
		//#if (SCREENGROUP_128x128xSpecial || SCREENGROUP_128x160xSpecial)
			int hgw = 0;
			int hgh = Resources.scoreStringHeight();
		//#else
			int hgw = PicDrawer.GetPicWidth(PicID.nPicID_hourglass);
			int hgh = PicDrawer.GetPicHeight(PicID.nPicID_hourglass);
		//#endif

		if (Hero.timeAlarm) {
			int wt = hgw + Resources.scoreStringWidth("000");

			if (MTMCanvas.setClip(g, 3, 1, wt + 10, hgh + 4, true)) {
				g.setColor(0xff0000);
				g.fillRoundRect(3, 1, wt + 9, hgh + 3, 9, 9);
				g.setColor(0xffffff);
				g.drawRoundRect(3, 1, wt + 9, hgh + 3, 9, 9);
			}
		}
		y = 3 + ((hgh - Resources.scoreStringHeight()) >> 1);

		//#if (SCREENGROUP_128x128xSpecial || SCREENGROUP_128x160xSpecial)
			x = 10;
		//#else
			x = 10 + hgw;
			PicDrawer.DrawPic(g, 8, 3, PicID.nPicID_hourglass, Graphics.TOP|Graphics.LEFT);
		//#endif

		Resources.DrawScoreNumber(g, x, y, nLevelTicks, true, Graphics.TOP|Graphics.LEFT);
		Resources.DrawScoreNumber(g, MTMCanvas.width - 5, y, Hero.m_nTotalScores, false, Graphics.TOP|Graphics.RIGHT);

		// lives
		x = 1;
		y = MTMCanvas.height - PicDrawer.GetPicHeight(PicID.nPicID_life) - 1;
		hgw = PicDrawer.GetPicWidth( PicID.nPicID_life ) + 1;
		for (int l = 0; l<Hero.m_nLives; l++, x+= hgw) {
			PicDrawer.DrawPic(g, x, y, PicID.nPicID_life, Graphics.TOP|Graphics.LEFT);
		}

		// information text
		if (nCompleteTicks > 0) {
			if (Hero.m_nImmortalTicks > 0)
				Resources.DrawString(g, StringsID.nStrID_TimeOver, hw, hh - 20, Graphics.TOP|Graphics.HCENTER, Resources.nOptFlg_BoldFnt);
			else {
				GameMenu.drawText(g, hw, hh, Graphics.VCENTER|Graphics.HCENTER);
			}
		}
		else if(Hero.m_nDeathTicks > 0 && Hero.m_nLives < 0) {
			Resources.DrawString(g, StringsID.nStrID_GameOver, hw, hh, Graphics.TOP|Graphics.HCENTER, Resources.nOptFlg_BoldFnt);
		}
	}

	/**
	 *
	 */
	//#if BACK_BUFFER_FULL || BACK_BUFFER_LEVEL
	public static void clearBackBuffer() {
		iBackBuffer = null;
		gBackBuffer = null;
		System.gc();
	}

	public static void prepareBackBuffer() {
		// cut displayable part of game play screen
		bbWidth = (MTMCanvas.width / TileDrawer.nTileWidth + 2) * TileDrawer.nTileWidth;
		if (bbWidth > gps_width) {
			bbWidth = gps_width;
		}

		bbHeight = (MTMCanvas.height / TileDrawer.nTileWidth + 2) * TileDrawer.nTileWidth;
		if (bbHeight > gps_height) {
			bbHeight = gps_height;
		}

		bbLeft = ((nWndCX - (MTMCanvas.width >> 1)) / TileDrawer.nTileWidth) * TileDrawer.nTileWidth;
		bbTop = ((nWndCY - (MTMCanvas.height >> 1)) / TileDrawer.nTileWidth) * TileDrawer.nTileWidth;
		int right, bottom;

		if (bbLeft < 0) {
			bbLeft = 0;
			right = bbWidth;
		}
		else {
			right = bbLeft + bbWidth;

			if (right >= gps_width) {
				right = gps_width;
				bbLeft = right - bbWidth;
			}
		}

		if (bbTop < 0) {
			bbTop = 0;
			bottom = bbHeight;
		}
		else {
			bottom = bbTop + bbHeight;

			if (bottom >= gps_height) {
				bottom = gps_height;
				bbTop = bottom - bbHeight;
			}
		}

		// convert coordinates to tile's indexes
		int tile_l = bbLeft / TileDrawer.nTileWidth;
		int tile_r = right / TileDrawer.nTileWidth;
		int tile_t = bbTop / TileDrawer.nTileWidth;
		int tile_b = bottom / TileDrawer.nTileWidth;

		// Background
		iBackBuffer = null;
		gBackBuffer = null;
		System.gc();

		iBackBuffer = Image.createImage(bbWidth, bbHeight);
		gBackBuffer = iBackBuffer.getGraphics();

		for (int i = tile_l, x = 0; i < tile_r; i++, x += TileDrawer.nTileWidth) {
			for (int j = tile_t, y = 0; j < tile_b; j++, y += TileDrawer.nTileWidth) {
				//#if TILESTYPE_SEPARATE
					TileDrawer.DrawTile(gBackBuffer, x, y, bTilesMatrix[i][j]);
				//#else
				int n_tile_id = bTilesMatrix[i][j];
				if ((n_tile_id >= 0) && (n_tile_id < TileDrawer.mTiles.length)) {
					PicIndex picIndex = PicDrawer.mIndex[TileDrawer.mTiles[n_tile_id].nPicID];
					gBackBuffer.setClip(x, y, picIndex.w, picIndex.h);
					gBackBuffer.drawImage(PicDrawer.mImages[picIndex.nImgID], x - picIndex.x, y - picIndex.y, Graphics.TOP | Graphics.LEFT);
				}
				//#endif
			}
		}
	}
	//#endif

	/**
	 *
	 */
	public static int platformType(int nPicID, boolean isLadder) {
		switch (nPicID) {
			// left tile
			case PicID.nPicID_p0_dn_lev0_top_off:
			case PicID.nPicID_p0_dn_lev0_top_on:
			case PicID.nPicID_p0_dn_lev1_top_off:
			case PicID.nPicID_p0_dn_lev1_top_on:
			case PicID.nPicID_p0_dn_lev2_top_off:
			case PicID.nPicID_p0_dn_lev2_top_on:
			//#if !IMGGROUP_12x12xLimited
				case PicID.nPicID_p1_dn_lev0_top_off:
				case PicID.nPicID_p1_dn_lev0_top_on:
				case PicID.nPicID_p1_dn_lev1_top_off:
				case PicID.nPicID_p1_dn_lev1_top_on:
				case PicID.nPicID_p1_dn_lev2_top_off:
				case PicID.nPicID_p1_dn_lev2_top_on:
				case PicID.nPicID_p2_dn_lev0_top_off:
				case PicID.nPicID_p2_dn_lev0_top_on:
				case PicID.nPicID_p2_dn_lev1_top_off:
				case PicID.nPicID_p2_dn_lev1_top_on:
				case PicID.nPicID_p2_dn_lev2_top_off:
				case PicID.nPicID_p2_dn_lev2_top_on: 
					if (isLadder) return PicID.nPicID_ladder_tile_left;
			//#else
				if (isLadder) return ~PicID.nPicID_ladder_tile_full;
			//#endif

			// right tile
			case PicID.nPicID_p0_up_lev0_top_off:
			case PicID.nPicID_p0_up_lev0_top_on:
			case PicID.nPicID_p0_up_lev1_top_off:
			case PicID.nPicID_p0_up_lev1_top_on:
			case PicID.nPicID_p0_up_lev2_top_off:
			case PicID.nPicID_p0_up_lev2_top_on:
			//#if !IMGGROUP_12x12xLimited
				case PicID.nPicID_p1_up_lev0_top_off:
				case PicID.nPicID_p1_up_lev0_top_on:
				case PicID.nPicID_p1_up_lev1_top_off:
				case PicID.nPicID_p1_up_lev1_top_on:
				case PicID.nPicID_p1_up_lev2_top_off:
				case PicID.nPicID_p1_up_lev2_top_on:
				case PicID.nPicID_p2_up_lev0_top_off:
				case PicID.nPicID_p2_up_lev0_top_on:
				case PicID.nPicID_p2_up_lev1_top_off:
				case PicID.nPicID_p2_up_lev1_top_on:
				case PicID.nPicID_p2_up_lev2_top_off:
				case PicID.nPicID_p2_up_lev2_top_on: 
					if (isLadder) return PicID.nPicID_ladder_tile_right;
			//#else
				if (isLadder) return ~PicID.nPicID_ladder_tile_full;
			//#endif


			// horizontal tile
			case PicID.nPicID_p0_hz_lev0_top_off: 
			case PicID.nPicID_p0_hz_lev0_top_on: 
			case PicID.nPicID_p0_hz_lev1_top_off: 
			case PicID.nPicID_p0_hz_lev1_top_on: 
			case PicID.nPicID_p0_hz_lev2_top_off: 
			case PicID.nPicID_p0_hz_lev2_top_on: 
			//#if !IMGGROUP_12x12xLimited
				case PicID.nPicID_p1_hz_lev0_top_off: 
				case PicID.nPicID_p1_hz_lev0_top_on: 
				case PicID.nPicID_p1_hz_lev1_top_off: 
				case PicID.nPicID_p1_hz_lev1_top_on: 
				case PicID.nPicID_p1_hz_lev2_top_off: 
				case PicID.nPicID_p1_hz_lev2_top_on: 
				case PicID.nPicID_p2_hz_lev0_top_off: 
				case PicID.nPicID_p2_hz_lev0_top_on: 
				case PicID.nPicID_p2_hz_lev1_top_off: 
				case PicID.nPicID_p2_hz_lev1_top_on: 
				case PicID.nPicID_p2_hz_lev2_top_off: 
				case PicID.nPicID_p2_hz_lev2_top_on: 
					if (isLadder) return PicID.nPicID_ladder_tile_full;
				return PicID.nPicID_ladder_tile_right; // or: return PicID.nPicID_ladder_tile_left;
			//#else
				if (isLadder) return PicID.nPicID_ladder_tile_full;
				return ~PicID.nPicID_ladder_tile_full;
			//#endif
		}
		return PicID.nPicID_ladder_tile_full;
	}

	/**
	 *
	 */
	public static int isFullTile(int left, int top, int right, int bottom, boolean isLadder) {
		if (left >= 0 && top >= 0) {
			int n_blk_left = left / TileDrawer.nTileWidth;
			int n_blk_right = right / TileDrawer.nTileWidth;
			int n_blk_top = top / TileDrawer.nTileWidth;
			int n_blk_bottom = bottom / TileDrawer.nTileWidth;

			if ((n_blk_right < bTilesMatrix.length) && (n_blk_bottom < bTilesMatrix[0].length)) {
				for (int n_x = n_blk_left; n_x <= n_blk_right; n_x++) {
					int n_tile = bTilesMatrix[n_x][n_blk_top];
					TileIndex tile = TileDrawer.mTiles[n_tile];

					int type = platformType(tile.nPicID, isLadder);
					if (type != PicID.nPicID_ladder_tile_full) return type;
				}
			}
		}
		return PicID.nPicID_ladder_tile_full;
	}

	/**
	* Method casts a ray between two points (x1,y1) and (x2,y2)
	* <I>Note: this mothod can be used as in the isMarvWithInRange so in the other places for checking if point is reachable</I>
	* @param x1 the x coordinate of the first point
	* @param y1 the y coordinate of the first point
	* @param x2 the x coordinate of the second point
	* @param y2 the y coordinate of the second point
	* @return true if points are within range and false otherwise
	*/
	public static boolean isPointsWithInRange(int x1, int y1, int x2, int y2) {
		if (x1 >= 0 && y1 >= 0) {
			int xn = x1 / TileDrawer.nTileWidth;
			int yn = y1 / TileDrawer.nTileWidth;
			int xk = x2 / TileDrawer.nTileWidth;
			int yk = y2 / TileDrawer.nTileWidth;

			if ((xk < bTilesMatrix.length) && (yk < bTilesMatrix[0].length) && (xn < bTilesMatrix.length) && (yn < bTilesMatrix[0].length)) {
				// Bresenhamm algorithm (fast drawing of line)
				int  dx, dy, s, sx, sy, kl, incr1, incr2;
				boolean swap = false;

				// calculating of increments and steps
				sx = 0;
				if ((dx = xk - xn) < 0) {
					dx = -dx;
					sx--;
				}
				else if (dx > 0) sx++;

				sy = 0;
				if ((dy = yk-yn) < 0) {
					dy = -dy;
					sy--;
				}
				else if (dy > 0) sy++;

				// calculation of incline
				if ((kl = dx) < (s = dy)) {
					dx = s;
					dy = kl;
					kl = s;
					swap = true;
				}

				s = (incr1 = dy << 1) - dx; // incr1 - constant of recalculation difference 
											// when current s < 0  and
											// s - inital value of difference
				incr2 = dx << 1;            // constant of recalculation difference when current s >= 0
   
				if (xn > -1 && xn < bTilesMatrix.length && yn > -1 && yn < bTilesMatrix[xn].length && tileType(TileDrawer.mTiles[bTilesMatrix[xn][yn]].nPicID) == PicID.nPicID_bg10) {
					return false;        // first pixel of vector
				}

				while (--kl >= 0) {
					if (s >= 0) {
						if (swap) xn += sx;
						else yn += sy;
						s -= incr2;
					}

					if (swap) yn += sy;
					else xn += sx;
					s += incr1;

					if (xn > -1 && xn < bTilesMatrix.length && yn > -1 && yn < bTilesMatrix[xn].length && tileType(TileDrawer.mTiles[bTilesMatrix[xn][yn]].nPicID) == PicID.nPicID_bg10) {
						return false;     // current point of vector
					}
				}
			}
		}
		return true;
	}

	/**
	 *
	 */
	public static int tileType(int nPicID) {
		switch (nPicID) {
			case PicID.nPicID_p0_hz_lev0_top_off: 
			case PicID.nPicID_p0_hz_lev0_top_on: 
			//#if !IMGGROUP_12x12xLimited
				case PicID.nPicID_p1_hz_lev0_top_off: 
				case PicID.nPicID_p1_hz_lev0_top_on: 
				case PicID.nPicID_p2_hz_lev0_top_off: 
				case PicID.nPicID_p2_hz_lev0_top_on: 
			//#endif
				return PicID.nPicID_p0_hz_lev0_top_off;

			case PicID.nPicID_p0_hz_lev1_top_off: 
			case PicID.nPicID_p0_hz_lev1_top_on: 
			//#if !IMGGROUP_12x12xLimited
				case PicID.nPicID_p1_hz_lev1_top_off: 
				case PicID.nPicID_p1_hz_lev1_top_on: 
				case PicID.nPicID_p2_hz_lev1_top_off: 
				case PicID.nPicID_p2_hz_lev1_top_on: 
			//#endif
				return PicID.nPicID_p0_hz_lev1_top_off;

			case PicID.nPicID_p0_hz_lev2_top_off: 
			case PicID.nPicID_p0_hz_lev2_top_on: 
			//#if !IMGGROUP_12x12xLimited
				case PicID.nPicID_p1_hz_lev2_top_off: 
				case PicID.nPicID_p1_hz_lev2_top_on: 
				case PicID.nPicID_p2_hz_lev2_top_off: 
				case PicID.nPicID_p2_hz_lev2_top_on: 
			//#endif
				return PicID.nPicID_p0_hz_lev2_top_off;

			case PicID.nPicID_p0_hz_lev2_bot_off: 
			case PicID.nPicID_p0_hz_lev2_bot_on: 
			//#if !IMGGROUP_12x12xLimited
				case PicID.nPicID_p1_hz_lev2_bot_off: 
				case PicID.nPicID_p1_hz_lev2_bot_on: 
				case PicID.nPicID_p2_hz_lev2_bot_off: 
				case PicID.nPicID_p2_hz_lev2_bot_on: 
			//#endif
				return PicID.nPicID_p0_hz_lev2_bot_off;
		}

		//#if !IMGGROUP_12x12xLimited
			if ((nPicID >= PicID.nPicID_bg10) && (nPicID <= PicID.nPicID_bg17))
		//#else
			if (nPicID == PicID.nPicID_bg10)
		//#endif
		{
			return PicID.nPicID_bg10;
		}

		//#if !IMGGROUP_12x12xLimited
			if (((nPicID >= PicID.nPicID_bg00) && (nPicID <= PicID.nPicID_bg03)) || ((nPicID >= PicID.nPicID_bgs1) && (nPicID <= PicID.nPicID_bgs11)) || (nPicID == PicID.nPicID_bg5))
		//#else
			if(nPicID == PicID.nPicID_bg5)
		//#endif
		{
			return PicID.nPicID_bg5;
		}

		return -1;
	}

	/**
	 *
	 */
	public static int checkTile(int x, int y, int last, byte direction) {
		int n_tile = bTilesMatrix[x][y];
		int tile = tileType(TileDrawer.mTiles[n_tile].nPicID);

		if (tile != last) {
			if (tile == PicID.nPicID_bg10) {
				if (((direction == Constants.PLATFORM_UP) && (last == PicID.nPicID_p0_hz_lev0_top_off)) || ((direction == Constants.PLATFORM_DOWN) && (last == PicID.nPicID_p0_hz_lev1_top_off))) {
					last = PicID.nPicID_bg10;
				}
				else {
					return -1;
				}
			}
			else if (last == PicID.nPicID_bg10) {
					if ((direction == Constants.PLATFORM_UP) && (tile == PicID.nPicID_p0_hz_lev0_top_off)) {
						last = PicID.nPicID_p0_hz_lev0_top_off;
					}
					else if((direction == Constants.PLATFORM_DOWN) && (tile == PicID.nPicID_p0_hz_lev1_top_off)) {
						last = PicID.nPicID_p0_hz_lev1_top_off;
					}
					else {
					   return -1;
					}
			}
			else {
				return -1;
			}
		}

		// look up
		if ((direction == Constants.PLATFORM_UP) && (y-1 >= 0)) {
			n_tile = bTilesMatrix[x][y-1];
			tile = tileType(TileDrawer.mTiles[n_tile].nPicID);
		}
		// look down
		else if((direction == Constants.PLATFORM_DOWN) && (y+1 < bTilesMatrix[0].length)) {
			n_tile = bTilesMatrix[x][y+1];
			tile = tileType(TileDrawer.mTiles[n_tile].nPicID);
		}
		else {
			tile = PicID.nPicID_bg5;
		}

		if (tile == PicID.nPicID_bg10) {
			return -1;
		}

		return last;
	}
   
   /**
    * Gets left and right edges of platform which is going through a point (x,y)
    * @param x the x coordinate of checking point
    * @param y the y coordinate of checking point
    * @param direction
    * @return returns left and right adges of platform in the format <br><code>((left edge << 16) & 0xFFFF0000) | (right edge & 0x0000FFFF)</code> 
    * or -1 if edges can not be found
    */
	public static int getPlatfomEdges(short x, short y, byte direction) {
		if (x >= 0 && y >= 0) {
			int px = x / TileDrawer.nTileWidth;
			int py;
			if (direction == Constants.PLATFORM_UP) {
				py = y / TileDrawer.nTileWidth;
			}
			else {
				py = ( y - 1 ) / TileDrawer.nTileWidth;
			}

			int i, left, right, n_tile;
			int tile, last;

			if ((px < bTilesMatrix.length) && (py < bTilesMatrix[0].length)) {
				left = right = px;

				// look to left side
				n_tile = bTilesMatrix[px][py];
				last = tileType(TileDrawer.mTiles[n_tile].nPicID);

				if ((last == -1) || (last == PicID.nPicID_bg5)) {
					return -1;
				}

				for (i=px-1; i>=0; i--) {
					last = checkTile(i, py, last, direction);
					if (last == -1) {
						break;
					}
					else {
						left = i;
					}
				}

				// look to right side
				n_tile = bTilesMatrix[px][py];
				last = tileType(TileDrawer.mTiles[n_tile].nPicID);

				for (i=px+1; i<bTilesMatrix.length; i++) {
					last = checkTile(i, py, last, direction);
					if (last == -1) {
						break;
					}
					else {
						right = i;
					}
				}

				return ((((left*TileDrawer.nTileWidth) << 16) & 0xFFFF0000) | (((right*TileDrawer.nTileWidth) + TileDrawer.nTileWidth) & 0x0000FFFF));
			}
		}

		return -1;
	}

   /**
    * Get y coordinate of first collision Slime with platform after falling
    * @param x the inital x coordinate of Slime befor falling
    * @param y the inital y coordinate of Slime befor falling
    * @return returns y coordinate of first collision Slime with platform after falling
    */
	public static short monsterCollision(int x, int y) {
		if ((x >= 0) && (y>=0)) {
			int start_x = x / TileDrawer.nTileWidth;
			int start_y = y / TileDrawer.nTileWidth + 1;
			TileIndex tile;

			if ((start_x < bTilesMatrix.length) && (start_y < bTilesMatrix[0].length)) {
				for (int n_y = start_y; n_y < bTilesMatrix[0].length; n_y++) {
					tile = TileDrawer.mTiles[bTilesMatrix[start_x][n_y]];

					if ((tile.bOptions & TileDrawer.bOpt_Collisable) != 0) { // detect collision with collisable tile
						// solid blocks is have'nt mask
						if((tile.bOptions & TileDrawer.bOpt_HaveMask) == 0) {
							return (short)(n_y * TileDrawer.nTileWidth);
						}
						int x_per = (x - (start_x * TileDrawer.nTileWidth)) / TileDrawer.nTileHVDiv;
						return (short)(n_y * TileDrawer.nTileWidth + tile.bHeight[x_per]);
					}
				}
			}
		}
		// outside of map is solid
		return (short)(bTilesMatrix[0].length * TileDrawer.nTileWidth);
	}

	// constants
	//#if IMGGROUP_12x12 || IMGGROUP_12x12xLimited
		// no additional futures in Jump action
		public final static long c_nGravityPerTick = FP.div(2*12 + 1, 18);   // 1.(3)
	//#elif IMGGROUP_18x18
		public final static long c_nGravityPerTick = FP.toFP(2);             // 2
	//#elif IMGGROUP_24x24 || IMGGROUP_24x24xLimited
		public final static long c_nGravityPerTick = FP.div(2*24 - 4, 18);   // 2.(6)
	//#elif IMGGROUP_36x36
		public final static long c_nGravityPerTick = FP.toFP(4);             // 4
	//#endif

	public static final int GWRET_NONE = 0;
	public static final int GWRET_TIME_OVER = 0x100;
	public static final int GWRET_COMPLETE = 0x101;
}
